#!/usr/bin/ruby	
#Copyright (C) 2010  Chris Patten
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#Contact:		cpatten[a-t]sunera.com
#			chris[a-t]packetresearch.com
#Blog:			http://security.sunera.com
#Code Repo:		http://code.google.com/p/vconn/
#Purpose: 		A utility to perform virtual host web discovery against systems
#Date: 			December 17, 2009
#Name: 			Vconn	
#Version: 		0.0.1
#Dependencies: 		http://www.bing.com/developers API key

# Define includes
require 'getoptlong'
require 'ftools'
require 'net/http'
require 'resolv'
require 'rexml/document'

# Define Constants
	BingKey		= "Insert http://www.bing.com/developers API key here"
					# insert the api key obtained from
					# http://www.bing.com/developers	

# Define global variables
	version		= "0.0.1"	# used by the --version or -v option handler
	ip_args		= []		# stores the list of arguments of --target or -t
	infile_args	= []		# stores the list of arguments of --input-file or -f
	target		= []		# stores the target value(s)
	ipAlias		= []		# stores the domain alias IPs
	have_options_f	= false		# set to true when at least one option is used 
	ex_options_n	= 0		# used to store the number of mutually exclusive
					# options, when > 1, the script will terminate

# Define Usage/Help method
	def printusage(error_code)
		print "vconn -- Utility to discover virtual web hosts\n"
		print "Note:  This program requires a http://www.bing.com/developers API key\n\n"
		print "Usage: ruby ./vconn.rb [POSIX or GNU style options]...\n"
		print "Mandatory arguments to long/short options\n"
		print "-t	--target (IP or Domain)\n"
		print "-f	--input-file (File containing a list of IP and/or Domains)\n"
		print "-h	--help\n"
		print "-u	--usage\n"
		print "-v	--version\n\n"
		exit(error_code)
	end

# Define validation routine for file contents
	def validateFile(arg)
		if File.zero?(arg) 
			exit(0)
		end
	end

# Define method for making the http(s) request
	def makeRequest(target)
		if $BingKey == "" then
			$stderr.print "The http://www.bing.com/developers API key seems to be missing!\n\n"
			exit(0)
		else
			target.each do |line|
				if line !~ /^(\d+\.){3}\d+$/ 
					nameLookup(line)
				else
					url = "http://api.search.live.net/xml.aspx?Appid=#{BingKey}&query=ip:#{line.to_s.strip}&sources=web"
					res = Net::HTTP.get_response(URI.parse(url)).body
					parseXML(res)
				end
			end
		end
	end
	
# Define method to parse the http xml reponse
	def parseXML(res)
		doc = REXML::Document.new(res)
		ipSearchTerm = []
		links = []
		doc.elements.each('SearchResponse/Query/SearchTerms') do |ele|
			ipSearchTerm << ele.text
		end
		doc.elements.each('SearchResponse/web:Web/web:Results/web:WebResult/web:DisplayUrl') do |ele|
			links << ele.text
		end
			
		ipSearchTerm.each do |ipSearchTerm|
			searchterm = ipSearchTerm
			searchterm["ip:"] = ""
			print "Acquiring virtual hosts for #{searchterm} \n"
			print "-----------------------------------------------\n"
			links.each do |links|
				print "#{links.to_s.strip}\n"
			end
			print "\n"
		end
	end

# Define method to perform name lookup
	def nameLookup(line)
		hostentry = Resolv.getaddresses(line)
		print "Acquiring aliases for #{line.to_s.strip} \n"
		print "-----------------------------------------------\n"
		puts hostentry
		print "\n"
		ipAlias = hostentry
		ipAlias.each do |entry|
			makeRequest(entry)
		end
	end

# Prepare commandline options parser
	opts = GetoptLong.new(
		[ '--target',		'-t',   GetoptLong::REQUIRED_ARGUMENT ],
		[ '--input-file',	'-f',   GetoptLong::REQUIRED_ARGUMENT ],
		[ '--help',		'-h',   GetoptLong::NO_ARGUMENT ],
		[ '--usage',		'-u',   GetoptLong::NO_ARGUMENT ],
		[ '--version',		'-v',   GetoptLong::NO_ARGUMENT ]
	)	

# Parse commandline options
begin
	opts.each do |opt, arg|
		case opt
			when "--target" 
				ip_args = arg.to_s.strip	
				target = ip_args
				ex_options_n   += 1
				have_options_f = true   					
			when "--input-file"
				validateFile(arg)
				infile_args = File.read(arg.to_s).to_s.strip
				target = infile_args
				ex_options_n   += 1
				have_options_f = true 
			when "--logfile"
				log_args = File.read(arg.to_s).to_s.strip
				have_options_f = true 
			when "--help"
				printusage(0)
			when "--usage"
				printusage(0)
			when "--version"
				print "vconn: version ", version, "\n"
				exit(0)
		end				
	end
	
# Test for mutually exclusive options: --target and --input-file
        if ex_options_n > 1
		$stderr.print "vconn: cannot use --target (-t) and --input-file (-f) together\n\n"
		printusage(1)
        end

# Test for missing options
	if have_options_f == false
		printusage(1)
	else
		makeRequest(target)
	end
	
rescue 
	printusage(1)
end

